home *** CD-ROM | disk | FTP | other *** search
- /* A few simple conversions for monochrome & 16-color PCX files
-
- PCXUTILS is meant to deal with pcx-files produced by DrawPerfect,
- WP Presentations, Harvard Graphics and GhostScript.
- Output should be usable for WordPerfect and TeX.
- Problems which occur with these programs:
- - GhostScript doesn't always produce an even n. of bytes
- - Harvard Graphics does something funny with the palette
- - WordPerfect and some other programs seem to compute bytes/line from
- image width instead of reading it directly from the header
- - Many graphics programs can't produce monochrome bitmaps, causing
- unnecessarily large filesizes
-
- PCXUTILS does the following:
- /i: invert
- /m: color to monochrome: every non-white color becomes black;
- use this option only when the bitmap has a standard palette
- apply successive /i /m /i transformations if you want
- to map every non-black color to white
- /r: round image size up to even n. of bytes per line
- /p+: standardize palette by interpreting existing palette; equivalent to /p
- /p-: standardize palette and ignore existing palette
- Only the first switch is honored.
- Irrespective of options:
- - round bytes/line up to even number (irrespective of image size)
- - reduce bytes/line to smallest possible number
-
- Compiler: Turbo C++ 3.0
- */
-
- /* standard header files */
- #include <stdio.h>
- #include <string.h>
- #include <stdlib.h>
- #include <alloc.h>
- #include <dos.h>
- #include <dir.h>
- #include <errno.h>
-
- #define FALSE 0
- #define TRUE 1
- #define LOBYTE(x) (*(unsigned char *)&(x))
- #define HIBYTE(x) (*(((unsigned char *)&(x))+1))
-
- void errexit0 (char *message);
- void errexit1 (char *message);
-
- int prcount, swcount, parinx, swinx;
- void cmdline(void); /* find command-line switch and parameter */
-
- void helpinfo(void);
- void make_palette(void);
-
- /* PCX file header */
- typedef struct
- { char manufacturer;
- char version;
- char encoding;
- char bits_per_pixel;
- int xmin,ymin;
- int xmax,ymax;
- int hres,vres;
- unsigned char palette[48];
- char reserved;
- char color_planes;
- int bytes_per_line;
- int palette_type;
- char filler[58];
- } PCXHEAD;
-
- PCXHEAD header;
-
- /* The following typedefs and data make it possible to determine
- pixel values without actual unpacking */
- typedef union
- { unsigned long l;
- unsigned char c[4];
- } MASK; /* for each colorplane one byte */
- typedef MASK MASKARRAY[8];
- MASKARRAY masks; /* masks to isolate each pixel for each bit position */
- MASKARRAY maskpalette[16]; /* (color code, bit position) -> MASK */
- MASKARRAY newpalette[16]; /* (new color code, bit position) -> MASK */
-
- /* available processing options */
- enum options { opt_tomono, opt_round, opt_invert, opt_pal_ignore,
- opt_pal_arrng, opt_none };
- int pr_opt; /* selected processing option */
-
- FILE *in, *out;
-
- /* properties input file: */
- int old_bpl; /* old value for bytes_per_line */
- int bpl_tot; /* bytes per line times color planes */
- int old_width; /* old picture width */
- int monoin; /* input file is mono */
- int chg_bpl; /* bytes per line requires changing */
- int palet_funny; /* palette anomalies; implies !mono && header.version!=3 */
- char pcxpath[MAXPATH], oldpath[MAXPATH]; /* full filenames */
- char old_pal[48]; /* old palette */
- unsigned char *const hpal = (unsigned char *)&header.palette;
- /* for easy reference to header palette */
- char const std_pal[48] = /* a more or less standard EGA palette */
- { 0x0,0x0,0x0, 0x0,0x0,0x80, 0x0,0x80,0x0, 0x0,0x80,0x80,
- 0x80,0x0,0x0, 0x80,0x0,0x80, 0x80,0x80,0x0, 0x80,0x80,0x80,
- 0x40,0x40,0x40, 0x0,0x0,0xff, 0x0,0xff,0x0, 0x0,0xff,0xff,
- 0xff,0x0,0x0, 0xff,0x0,0xff, 0xff,0xff,0x0, 0xff,0xff,0xff };
-
- typedef unsigned char *LINE;
- LINE plane[4]; /* up to 4 bitplanes, yet to be allocated */
- unsigned char *pl; /* pointer into plane */
-
- void main (int argc, char *argv[])
-
- { int c,i,j,k,l;
- union { unsigned char ch[2]; unsigned int intg; } endmask;
- MASK eachplane; /* one byte from each plane */
- MASK thispixel;
- cmdline();
- if (prcount>1) puts ("Excess command-line parameters ignored");
- if (swcount>1) puts ("Excess command-line switches ignored");
-
- /* header input file */
- if (prcount<1) helpinfo();
- { char fndrive[MAXDRIVE];
- char fndir[MAXDIR];
- char fnfile[MAXFILE];
- fnsplit
- (argv[parinx],(char *)fndrive,(char *)fndir,(char *)fnfile,NULL);
- fnmerge (pcxpath,(char *)fndrive,(char *)fndir,(char *)fnfile,".PCX");
- fnmerge (oldpath,(char *)fndrive,(char *)fndir,(char *)fnfile,".OLD");
- }
- in = fopen (pcxpath,"rb");
- if (!in) errexit0("Access failure input file");
- if (fread((char *)&header,1,sizeof(PCXHEAD),in)!=sizeof(PCXHEAD))
- errexit0("Failure to read PCX file");
- fclose(in); /* later, we reopen the file as *.old */
- if (header.manufacturer!=0x0a) errexit0("Not a PCX file");
- if (header.bits_per_pixel!=1) errexit0("Format not supported");
- /* this might be changed later on */
- if (header.color_planes==1) monoin = TRUE;
- else if (header.color_planes==4) monoin = FALSE;
- else errexit0("Format not supported");
-
- /* standard palette: 1rst color 0,0,0 last ff,ff,ff; all colors different */
- palet_funny=FALSE;
- memmove(old_pal,header.palette,48);
- if (header.version!=3 && !monoin)
- { for (i=0;i<3;i++) if (header.palette[i]!=0)
- { palet_funny=TRUE; break; }
- if (!palet_funny) for (i=45;i<48;i++) if (header.palette[i]!=255)
- { palet_funny=TRUE; break; }
- if (!palet_funny) for (i=0;i<48;i+=3) /* entries all different? */
- { for (j=0;j<i;j+=3)
- if (old_pal[i]==old_pal[j]
- && old_pal[i+1]==old_pal[j+1]
- && old_pal[i+2]==old_pal[j+2]) { palet_funny=TRUE; break; }
- if (palet_funny) break;
- }
- }
-
- /* image width and bytes_per_line */
- old_width=header.xmax-header.xmin+1;
- if (old_width<=0 || header.ymax<header.ymin)
- errexit0("Error: Picture has zero height or width");
- old_bpl = header.bytes_per_line;
- chg_bpl = (old_bpl%2) || (old_bpl*8-old_width*header.bits_per_pixel>15);
-
- in = NULL; out = NULL;
-
- /* decode switch; start comparing after first character ('/' or '-') */
- pr_opt = opt_none;
- if (swcount>0)
- { if (!stricmp(argv[swinx]+1,"r")) pr_opt = opt_round;
- else if (!stricmp(argv[swinx]+1,"i")) pr_opt = opt_invert;
- else if (!stricmp(argv[swinx]+1,"m")) pr_opt = opt_tomono;
- else if (!stricmp(argv[swinx]+1,"p+")||
- !stricmp(argv[swinx]+1,"p")) pr_opt = opt_pal_arrng;
- else if (!stricmp(argv[swinx]+1,"p-")) pr_opt = opt_pal_ignore;
- else helpinfo();
- }
-
- /* sort out actions; report to user */
-
- /* comments on header */
-
- if (palet_funny && pr_opt!=opt_pal_ignore && pr_opt!=opt_pal_arrng)
- puts("Non-standard palette;\n"
- "if results unsatisfactory try a preliminary PCXUTILS /p run");
- if (chg_bpl) puts ("Correcting bytes/line");
-
- /* options */
-
- if (pr_opt==opt_tomono)
- { if (monoin) { puts("Bitmap already mono"); pr_opt = opt_none; }
- else puts("Converting to monochrome");
- }
- else if (pr_opt==opt_round)
- { if (old_width*header.bits_per_pixel==8*old_bpl && !chg_bpl)
- { puts("Image width is already rounded to match bytes/line");
- pr_opt = opt_none; }
- else puts("Rounding image width");
- }
- else if (pr_opt==opt_invert) puts("Inverting colors");
-
- else if (pr_opt==opt_pal_ignore)
- { if (monoin) { puts("Monochrome bitmap"); pr_opt = opt_none; }
- else if (header.version==3)
- puts("No palette; bitmap gets standard palette");
- else if (!palet_funny) { puts("Palette ok"); pr_opt = opt_none; }
- else puts("Bitmap gets standard palette; original palette ignored");
- }
-
- else if (pr_opt==opt_pal_arrng)
- { if (monoin) { puts("Monochrome bitmap"); pr_opt = opt_none; }
- else if (header.version==3)
- { pr_opt = opt_pal_ignore;
- puts("No palette; bitmap gets standard palette");
- }
- else if (!palet_funny) { puts("Palette ok"); pr_opt = opt_none; }
- else puts("Palette will be standardized by rearranging original palette");
- }
- /* else pr_opt==opt_none */
-
- if (pr_opt==opt_none && !chg_bpl) { puts("Nothing to do"); exit(0); }
- fputs("Transforming ",stdout); puts(pcxpath);
- fputs("Original file will be copied to ",stdout); puts(oldpath);
-
- /* get confirmation */
- { fputs("Continue y/n? [y]",stdout);
- for (;;)
- { c = getc(stdin);
- if (c=='n'||c=='N') exit(0);
- else if (c=='j'||c=='J'||c=='y'||c=='Y'||c=='\n') break;
- }
- putchar('\n');
- }
-
- /* edit header; we already saved essential old info
- in old_bpl, old_pal, old_width and monoin */
-
- header.hres=header.vres=300;
- header.palette_type %= 2; /* 1==color/BW; 2==gray */
- if (pr_opt==opt_pal_ignore) memmove(header.palette,std_pal,48);
- if (pr_opt==opt_pal_arrng) make_palette();
- if (pr_opt==opt_pal_ignore || pr_opt==opt_pal_arrng) header.version = 5;
-
- if (chg_bpl) /* correct header.bytes_per_line */
- { if (old_bpl%2) header.bytes_per_line++; /* even n. of bytes per line */
- while (8*header.bytes_per_line-old_width*header.bits_per_pixel>15)
- header.bytes_per_line-=2;
- }
-
- /* offset window (0,0); round picture width */
- header.xmax=header.xmax-header.xmin; header.xmin=0;
- header.ymax=header.ymax-header.ymin; header.ymin=0;
- if (pr_opt==opt_round)
- header.xmax=8*header.bytes_per_line/header.bits_per_pixel-1;
-
- if (pr_opt==opt_tomono) header.color_planes=1; /* color or mono */
- if (pr_opt==opt_tomono||monoin)
- { hpal[0]=hpal[1]=hpal[2]=0; hpal[3]=hpal[4]=hpal[5]=0xff; }
-
- /* invert: interchange and invert palette entries */
- if (pr_opt==opt_invert)
- { unsigned char tmp;
- for (i=0,j=45;i<48;i+=3,j-=3) for (k=0;k<3;k++)
- { tmp=hpal[i+k]; hpal[i+k]=~hpal[j+k]; hpal[j+k]=~tmp; }
- }
-
- /* NOTE. Some cases only require changing the header.
- However, we unpack and repack the bitmap in all cases.
- */
-
- /* rename and reopen input file; open output file */
- if (unlink(oldpath)) if (errno!=ENOENT)
- errexit0("Failure to rename input file");
- if (rename(pcxpath,oldpath)) errexit0("Failure to rename input file");
- in = fopen (oldpath,"rb");
- if (!in) errexit1("Failure to open renamed input file");
- out = fopen (pcxpath,"wb");
- if (!out) errexit1("Failure to open output file");
-
- /* write edited header to output; skip input header */
- if (fwrite(&header,1,sizeof(PCXHEAD),out)!=sizeof(PCXHEAD))
- errexit1("Write failure");
- if (fseek(in,sizeof(PCXHEAD),SEEK_SET)) errexit1("Read failure");
-
- /* Allocate line buffer.
- When reading, we unpack planes contiguously and only refer to plane[0].
- Therefore, we set plane[1..3] now as required for output
- */
- k = (old_bpl<=header.bytes_per_line)?header.bytes_per_line:old_bpl;
- plane[0]=malloc(k*(monoin?1:4));
- if (!plane[0]) errexit1("Out of memory");
- if (!monoin)
- for (i=1;i<4;i++) plane[i]=&(plane[i-1][header.bytes_per_line]);
-
- /* Make mask to set strip at right to white. Recall that within one byte,
- the most significant bits represent the leftmost pixels */
- k=8*header.bytes_per_line-header.xmax-1;
- endmask.intg=0;
- if (k<8) i=1; /* left byte 0 */
- else {k-=8; endmask.ch[1]=0xff; i=0;} /* set right byte 0xff; 8 bits done */
- for (;k>0;k--) endmask.ch[i]=0x01|(endmask.ch[i]<<1);
-
- /* now reading, converting and writing data */
- for (j=header.ymin;j<=header.ymax;j++)
- { pl = plane[0];
- bpl_tot = old_bpl*(monoin?1:4);
- for (;pl<plane[0]+bpl_tot;) /* decompress RLE-compressed data */
- { c = fgetc(in) & 0xff;
- if ((c&0xc0)==0xc0) /* run length */
- { i=c&0x3f;
- c=fgetc(in); /* run byte */
- memset(pl,LOBYTE(c),i); pl += i;
- }
- else *(pl++)=c;
- }
- /* adjusting bytes per line */
- if (chg_bpl&&!monoin)
- { if (header.bytes_per_line>old_bpl) for (i=3;i>0;i--)
- memmove(plane[0]+i*header.bytes_per_line,plane[0]+i*old_bpl,old_bpl);
- else for (i=1;i<4;i++)
- memmove(plane[0]+i*header.bytes_per_line,plane[0]+i*old_bpl,old_bpl);
- }
- bpl_tot = header.bytes_per_line*(monoin?1:4);
-
- /* now requested transformation */
-
- /* inverting */
- if (pr_opt==opt_invert)
- { pl = plane[0];
- for (i=0;i<bpl_tot;i++,pl++) *pl = ~(*pl);
- }
-
- /* adjusting palette */
- else if (pr_opt==opt_pal_arrng)
- { for (i=0;i<header.bytes_per_line;i++)
- { /* combine bitplanes */
- for (k=0;k<4;k++) eachplane.c[k]=plane[k][i];
- /* process color of each of eight pixels */
- for (k=0;k<8;k++)
- { thispixel.l = eachplane.l&masks[k].l;
- for (l=0;l<16;l++) if (thispixel.l==maskpalette[l][k].l) break;
- if (l==16) errexit1 ("System error; please report");
- eachplane.l=(eachplane.l&~(masks[k].l))|newpalette[l][k].l;
- }
- /* redivide info over bitplanes */
- for (k=0;k<4;k++) plane[k][i]=eachplane.c[k];
- }
- }
-
- /* converting to monochrome */
- else if (pr_opt==opt_tomono) for (i=0;i<header.bytes_per_line;i++)
- plane[0][i]=plane[0][i]&plane[1][i]&plane[2][i]&plane[3][i];
-
- /* Remaining cases opt_round and opt_pal_ignore: no action.
- Make right strip white by or-ing with endmask */
-
- if (header.xmax+1<8*header.bytes_per_line)
- for (k=0;k<header.color_planes;k++)
- { plane[k][header.bytes_per_line-2]|=endmask.ch[0];
- plane[k][header.bytes_per_line-1]|=endmask.ch[1];
- }
-
- /* Compress and write. We compress each color plane separately,
- although this probably isn't necessary */
- for (k=0;k<header.color_planes;k++)
- { pl=plane[k];
- while (pl<(plane[k]+header.bytes_per_line))
- { for (i=1;pl[i-1]==pl[i]&&i<63
- &&(pl+i)<(plane[k]+header.bytes_per_line);i++);
- /* now pl[0]==pl[i-1], i<=63 and pl[i-1] belongs to plane[k] */
- if (i!=1||pl[0]>=0xc) /* run */
- { if (fputc(0xc0|i,out)==EOF) errexit1("Write failure");
- if (fputc(pl[0],out)==EOF) errexit1("Write failure");
- pl+=i;
- }
- else { if (fputc(pl[0],out)==EOF) errexit1("Write failure"); pl++; }
- }
- }
- } /* for j=ymin..ymax */
- fclose(out); fclose(in);
- }
-
- void errexit0 (char *message)
- { if (in) fclose(in); puts(message); exit(1); }
-
- /* after file.pcx is renamed to file.old, call the error exit below */
- void errexit1 (char *message)
- { if (out) { fclose(out); unlink(pcxpath); }
- if (in) fclose(in); rename(oldpath,pcxpath);
- errexit0(message);
- }
-
- void helpinfo(void)
- { puts("Usage:");
- puts("PCXUTILS [/i|/r|/n|/p|/p+|p-] filename");
- puts(" filename.PCX is copied to filename.OLD"
- " and replaced by a new version.");
- puts(" /i for inverting");
- puts(" /r for rounding image size up");
- puts(" /m for conversion to monochrome");
- puts(" /p, /p+ for standardizing palette"
- " (interpret existing palette info)");
- puts(" /p- for standardizing palette (ignore existing palette info)");
- exit(0);
- }
-
- void cmdline(void)
- /* command-line switches and parameters */
- { int i;
- if (_argc<=1) {prcount=swcount=0; parinx=swinx=0;}
- for (prcount=0,swcount=0,i=1;i<_argc;i++)
- { if (_argv[i][0]=='-' || _argv[i][0]=='/')
- { swcount+=1; if (swcount==1) swinx = i; }
- else { prcount+=1; if (prcount==1) parinx = i; }
- }
- }
-
- void make_palette(void)
- /* create a palette which includes 0/0/0 and ff/ff/ff - at positions
- 0 and 15 resp., in which each color is unique and which contains, if
- possible, all colors of the original palette.
- */
- { unsigned int scr_pal[16], int_pal[32];
- int pal_map[16]; /* mapping from old palette to new */
- unsigned int i,j,k;
- /* First, we construct a palette int_pal that contains all the original
- colors, also black and white, and enough greys to be sure of at least
- 16 DIFFERENT colors. The new palette scr_pal will be picked from int_pal.
- Thus, int_pal contains successively: black; white; old palette (= *hpal);
- greys.
- Colors are coded as single unsigned integers:
- We only retain the most significant 4 bits of each color,
- and consider those as hex digits of an unsigned integer */
- int_pal[0]=0; int_pal[1]=0xfff;
- for (i=0,j=0;i<16;i++) /* 2-17 */
- { int_pal[i+2]=256*(hpal[j++]/16);
- int_pal[i+2]+=16*(hpal[j++]/16);
- int_pal[i+2]+=hpal[j++]/16;
- }
- /* 18-31: 14 greys in the above coding */
- for (i=1;i<15;i++) int_pal[i+17]=0x111*i;
- /* fill scratch palette scr_pal with unique colors from int_pal */
- scr_pal[0]=0; scr_pal[1]=0xfff;
- for (i=2,j=0;i<16;i++)
- { /* find value for scr_pal[i], starting search at int_pal[j] */
- for (;;j++)
- { for (k=0; k<i && int_pal[j]!=scr_pal[k]; k++);
- if (k==i) {scr_pal[i] = int_pal[j]; break;}
- }
- }
- /* move white to place [15] */
- for (i=1;i<15;i++)
- scr_pal[i]=scr_pal[i+1];
- scr_pal[15]=0xfff;
- /* mapping from old palette to new; note that up to 2 colors
- from the old palette may be missing from the new, if the old
- palette didn't contain black and/or white.
- This possibility seems so remote that we are content with
- arbitrary mappings for the missing colors.
- */
- for (i=0;i<16;i++)
- { for (j=0;j<16;j++)
- if (scr_pal[j]==int_pal[i+2])
- {pal_map[i]=j; break;}
- if (j==16)
- pal_map[i]=8; /* arbitrary */
- }
- /* translate scr_pal to header palette */
- for (i=0,j=0;i<16;i++)
- { hpal[j++]=(scr_pal[i]/256)*17;
- hpal[j++]=((scr_pal[i]/16)%16)*17;
- hpal[j++]=(scr_pal[i]%16)*17;
- }
- /* now set up mask arrays for remapping colors */
- for (j=0;j<4;j++) masks[0].c[j]=0x01; /* mask for 1rst pixel */
- for (i=1;i<8;i++) masks[i].l=(masks[i-1].l)<<1; /* next pixel: left shift */
- /* maskpalette[j] contains color j appropriately shifted */
- for (j=0;j<16;j++)
- { maskpalette[j][0].c[0]=j&0x1;
- maskpalette[j][0].c[1]=(j&0x2)>>1;
- maskpalette[j][0].c[2]=(j&0x4)>>2;
- maskpalette[j][0].c[3]=(j&0x8)>>3;
- }
- for (j=0;j<16;j++) for (i=1;i<8;i++)
- maskpalette[j][i].l = maskpalette[j][i-1].l<<1;
- /* newpalette[j] contains color k=pal_map[j] appropriately shifted */
- for (j=0;j<16;j++)
- { k=pal_map[j];
- newpalette[j][0].c[0]=k&0x1;
- newpalette[j][0].c[1]=(k&0x2)>>1;
- newpalette[j][0].c[2]=(k&0x4)>>2;
- newpalette[j][0].c[3]=(k&0x8)>>3;
- }
- for (j=0;j<16;j++) for (i=1;i<8;i++)
- newpalette[j][i].l = newpalette[j][i-1].l<<1;
- }
-